/// <reference path="../routing/HttpRequest.js" />
/// <reference path="../utilities/FXDom.js" />
/// <reference path="../utilities/Markup.js" />
/// <reference path="../utilities/Post.js" />
/// <reference path="../utilities/Xpcom.js" />
///--------------------------------------------------------------------
/// <file name="QuickReply.js">
///   Adds a quick reply box to the view thread page, allowing users
///   to reply, preview, and go to the full reply pages. If they are
///   moderators, they can also close and open threads.
/// </file>
///--------------------------------------------------------------------

NAMFox.ns(function() { with (NAMFox) {

this.QuickReply = function(_$, htmlDocument, threadData) {
    /// <summary>Initializes a new instance of the QuickReply class.</summary>
    /// <param name="_$" type="FXDom">
    ///   The FXDom selector that allows this QuickReply instance
    ///   to select DOM elements from the page itself.
    /// </param>
    /// <param name="htmlDocument" type="Document">
    ///   The HTML Document object for the current page.
    /// </param>
    /// <param name="threadData">
    ///   Contains various data about the thread as well
    ///   as features added on the thread itself, like Quick Edit.
    /// </param>
    
    this._$ = _$;
    this._htmlDocument = htmlDocument;
    this._threadData = threadData;
};

QuickReply.replyMessageExpr = /<b>Note.*?<\/b>[\s\S]*?(?=<table id="posttable")/;
QuickReply.replyResponseOneExpr = /<table width=500[\s\S]*?\n<\/table>/;
QuickReply.replyResponseTwoExpr = /<table align=center width=450[\s\S]*?<\/table>/;
QuickReply.previewResponseExpr = /<div.*?id="message_preview".*?>[\s\S]+?<\/div><\/div>/;
QuickReply.previewErrorExpr = /<div.*?class="base".*?>[\s\S]+?Error in posting:[\s\S]+?<\/div>/;
QuickReply.spoilerExpr = /<div class="spoiler_header" style="display: none;">/g;

QuickReply.prototype = {
    _postRegExp: /<tr id="(\d+)"/g,
    _textArea: null,
    _suppressUnload: false,
    
    _appendPost: function(xhr) {
        /// <summary>
        /// Called after a reply has been sent to the server, this function
        /// will append the reply to the current page or refresh so that
        /// the user will see his or her reply.
        /// </summary>
        /// <param name="xhr" type="XMLHttpRequest">
        ///   The XMLHttpRequest object whose response text
        ///   contains the new post.
        /// </param>
        
        var response = xhr.responseText;
        this._reportStatus("Adding reply to page...");
        
        var oldPosts = this._htmlDocument.body.innerHTML.match(this._postRegExp);
        var newPosts = response.match(this._postRegExp);
        
        // If the td with id "row15" exists on the current page, then no more
        // replies can fit on the current page. Thus it's pointless to try to
        // retrieve the reply from the response text.
        if (newPosts.length > 1 &&
            this._$("#row15").length === 0 &&
            newPosts.length === oldPosts.length + 1) {
            try {
                var conditional = this._postRegExp.exec(response);
                
                // Try to find the last post ID by continuously executing the post regular
                // expression against the response text.
                var lastPostHeader = null;
                for ( ; conditional; conditional = this._postRegExp.exec(response)) {
                    lastPostHeader = conditional;
                }
                
                $.assert(
                    lastPostHeader,
                    "lastPostHeader, The last post must exist on a page."
                );
                
                var lastPostId = lastPostHeader[1];
                var lastPostMatch = response.match(
                    new RegExp('<tr.*?id="' + lastPostId + '".*?>[\\s\\S]*?(?=<tr class="threadlistbar)'));
                
                $.assert(lastPostMatch, "Could not find last post on the page after quick reply.");
                
                var postHtml = lastPostMatch[0].replace(QuickReply.spoilerExpr, "<div class=\"spoiler_header\" style=\"display: block;\">");
                
                this._$(".threadlistbar:last").before(postHtml);
                
                var post = new Post(this._$("td.authorcol:last").parent().get(0));
                post.setHtml(response);
                this._threadData.quickEdit.attachSingle(post);
                this._threadData.quickQuote.attachSingle(post);
            }
            catch (e) {
                $.error(e);
                this._clearQuickReplyMessage();
                this._htmlDocument.location.href = this._getLastReplyUrl();
            }
            finally {
                this._clearTextArea();
                this._endProcess();
            }
        }
        else {
            this._clearQuickReplyMessage();
            
            // If any of the conditions fail, then fallback to showing the last reply.
            this._htmlDocument.location.href = this._getLastReplyUrl();
        }
    },
    
    _beginProcess: function() {
        /// <summary>
        ///   Indicates in the user interface that
        ///   the reply is being submitted.
        /// </summary>
        
        this._$("#namfox-quick-reply-action-container input").attr("disabled", true);
        this._$("#namfox-quick-reply-loading-image").css("display", "inline");
        this._$("#namfox-quick-reply-response-message").html("");
        this._$("#namfox-quick-reply-status").attr("className", "");
    },
    
    _buildOptionElement: function(selectedValue, html, markup, value) {
        $.verbose("QUICK REPLY | Comparing selected value '" + selectedValue + "' with option value '" + value + "'.");
        var option = '<option html="' + html + '" markup="' + markup + '" value="' + value + '" ';
        
        if (selectedValue === value) {
            option += 'selected="selected"';
        }
        
        option += '>' + value + '</option>';
        
        return option;
    },
    
    _clearQuickReplyMessage: function() {
        /// <summary>
        ///   Prevents NAMFox from remembering the quick reply message
        ///   and clears the currently remembered message.
        /// </summary>
        
        this._suppressUnload = true;
        $.dataRepository.getData($.dataKeys.quickReplyMessage)[this._threadData.threadId] = null;
    },
    
    _clearTextArea: function() {
        /// <summary>
        ///   Clears the text area, usually done after a post is submitted.
        /// </summary>
        
        this._textArea.val("");
    },
    
    _endProcess: function() {
        /// <summary>
        ///   Indicates in the user interface that the reply has completed.
        /// </summary>
        
        this._$("#namfox-quick-reply-action-container input").attr("disabled", false);
        this._$("#namfox-quick-reply-loading-image").css("display", "none");
        this._reportStatus("");
    },
    
    _getLastReplyUrl: function() {
        /// <summary>
        ///   Gets the URL to find the last reply in the current thread.
        /// </summary>
        /// <returns type="String" />
        
        return $.url.forum("/lastreply.php?t=" + this._threadData.threadId);
    },

    _moveCaretToBeginning: function() {
        /// <summary>
        /// Moves the caret back to the beginning of the text area, so that quick quote
        /// will insert text before the default message.
        /// </summary>

        var element = this._textArea.get(0);
        element.setSelectionRange(0, 0);
    },
    
    _onCloseClicked: function() {
        /// <summary>
        ///   Called when the user clicks the close button.
        /// </summary>
        
        this._beginProcess();
        this._reportStatus("Closing thread...");
        this._processModeratorRequest("Close");
    },
    
    _onError: function(xhr, causedByTimeout, caller) {
        /// <summary>Called when an AJAX request fails.</summary>
        /// <param name="xhr" type="XMLHttpRequest">
        ///   The XMLHttpRequest that failed.
        /// </param>
        /// <param name="causedByTimeout" type="Boolean">
        ///   Specifies whether the error was caused by a time out.
        /// </param>
        /// <param name="caller" type="String">
        ///   A short description of the caller.
        /// </param>
        
        this._$("#namfox-quick-reply-status").attr("className", "namfox-error");
        this._endProcess();
        
        if (caller === "reply" && xhr.readyState >= 2) {
            this._htmlDocument.location.href = this._getLastReplyUrl();
            return;
        }
        
        if (causedByTimeout) {
            this._reportStatus("Submission timed out. Please try again.");
        }
        else {
            this._reportStatus("Request failed unexpectedly. Please try again.");
        }
        
        $.info("Quick Reply request failed during " + caller);
    },
    
    _onFullReplyClicked: function() {
        /// <summary>
        ///   Called when the user clicks the Go to Full Reply button.
        /// </summary>
        
        var form = '';
        form += '<form';
        form += '  id="namfox-quick-reply-full-form"';
        form += '  method="POST"';
        form += '  action="' + $.url.forum("/index.php") + '"';
        form += '  enctype="application/x-www-form-urlencoded"';
        form += '  style="display: none;">';
        form += '  <input type="hidden" name="subject" value="' + $.attrEncode(this._threadData.getSubjectForReply()) + '" />';
        form += '  <input type="hidden" name="body" value="' + $.attrEncode(this._textArea.val()) + '" />';
        form += '  <input type="hidden" name="threadid" value="' + this._threadData.threadId + '" />';
        form += '  <input type="hidden" name="submit" value="true" />';
        form += '  <input type="hidden" name="do" value="preview" />';
        form += '  <input type="hidden" name="function" value="post_reply" />';
        
        if (this._$("#namfox-quick-reply-signature").attr("checked")) {
            form += '<input type="hidden" name="usesig" value="Y" />';
        }
        
        var statusDropDownList = this._$("#namfox-quick-reply-bug-status");
        if (statusDropDownList.length > 0) {
            statusDropDownList = statusDropDownList.get(0);
            
            var selectedCaption = statusDropDownList.options[statusDropDownList.selectedIndex];
            form +=  '<input type="hidden" name="bug_status" value="' + $.attrEncode(selectedCaption.getAttribute("value")) + '" />';
        }
        else {
            var bugConfirmation = this._$("#namfox-quick-reply-bug-confirm");
            
            if (bugConfirmation.length > 0 && bugConfirmation.attr("checked")) {
                form += '<input type="hidden" name="confirm_bug" value="Y" />';
                confirmed = true;
            }
        }
        
        form += '</form>';
        
        this._$("body").append(form);
        this._$("#namfox-quick-reply-full-form").get(0).submit();
        
        this._clearQuickReplyMessage();
    },
    
    _onOpenClicked: function() {
        /// <summary>Called when the user clicks the open button.</summary>
        
        this._beginProcess();
        this._reportStatus("Opening thread...");
        this._processModeratorRequest("Open");
    },
    
    _onPreviewClicked: function(event) {
        /// <summary>Called when the user clicks the preview button.</summary>
        
        this._beginProcess();
        this._reportStatus("Getting preview...");
        var postData = {
            subject: $.urlEncode(this._threadData.getSubjectForReply()),
            body: $.urlEncode(this._textArea.val()),
            submit: true,
            "do": "preview",
            "function": "post_reply",
            threadid: this._threadData.threadId
        };
        
        if (this._$("#namfox-quick-reply-signature").attr("checked")) {
            postData.usesig = "Y";
        }
        
        $.post(
            $.url.forum("/index.php"),
            postData,
            $.createCallback(this._onPreviewSubmitted, this),
            $.createCallback(this._onError, this, "preview")
        );
    },
    
    _onPreviewSubmitted: function(xhr) {
        /// <summary>
        ///   Called when the preview request yields a meaningful response.
        /// </summary>
        /// <param name="xhr" type="XMLHttpRequest">
        ///   The XMLHttpRequest object that requested the preview.
        /// </param>

        
        var preview = xhr.responseText.match(QuickReply.previewResponseExpr);
        var error = xhr.responseText.match(QuickReply.previewErrorExpr);

        $.assert(preview, "Could not find a response that matched the preview regular expression");
        
        preview = preview[0].replace(QuickReply.spoilerExpr, "<div class=\"spoiler_header\" style=\"display: block;\">");

        if (error) {
            preview = error[0] + preview;
        }
        
        this._$("#namfox-quick-reply-response-message").html(preview);
        this._endProcess();
    },
    
    _onQuickReplyClicked: function(event) {
        /// <summary>
        ///   Called when the user clicks the quick reply button.
        /// </summary>
        
        this._beginProcess();
        this._reportStatus("Submitting reply...");
        
        var postData = {
            subject: $.urlEncode(this._threadData.getSubjectForReply()),
            body: $.urlEncode(this._textArea.val()),
            submit: true,
            "do": "submit",
            "function": "post_reply",
            threadid: this._threadData.threadId
        };
        
        var selectedCaption = null;
        var confirmed = false;
        var statusDropDownList = this._$("#namfox-quick-reply-bug-status");
        if (statusDropDownList.length > 0) {
            statusDropDownList = statusDropDownList.get(0);
            
            selectedCaption = statusDropDownList.options[statusDropDownList.selectedIndex];
            postData.bug_status = selectedCaption.getAttribute("value");
        }
        else {
            var bugConfirmation = this._$("#namfox-quick-reply-bug-confirm");
            
            if (bugConfirmation.length > 0 && bugConfirmation.attr("checked")) {
                postData.confirm_bug = "Y";
                confirmed = true;
            }
        }
        
        
        if (this._$("#namfox-quick-reply-signature").attr("checked")) {
            postData.usesig = "Y";
        }
        
        $.post(
            $.url.forum("/index.php"),
            postData,
            $.createCallback(this._onQuickReplySubmitted, this, selectedCaption, confirmed),
            $.createCallback(this._onError, this, "reply")
        );
    },
    
    _onQuickReplySubmitted: function(xhr, selectedCaption, confirmed) {
        /// <summary>
        ///   Called when a reply has been successfully submitted, or the
        ///   thread has been opened or closed.
        /// </summary>
        
        this._clearQuickReplyMessage();
        
        var response = xhr.responseText.match(QuickReply.replyResponseOneExpr);
        response = response || xhr.responseText.match(QuickReply.replyResponseTwoExpr);
        
        if (response) {
            response = response[0];
            var isOpenOperation = $.contains(response, "Thread opened");
            var isCloseOperation = $.contains(response, "Thread closed");
            
            if (isOpenOperation || isCloseOperation) {
                if (this._textArea.val() !== "") {
                    $.get(
                        this._getLastReplyUrl(),
                        null,
                        $.createCallback(this._appendPost, this),
                        null,
                        $.cacheHandling.bypassCache
                    );
                }
                else {
                    this._clearTextArea();
                    this._endProcess();
                }
                
                if (isCloseOperation) {
                    this._$(".forumnamearea p:first").attr(
                        "innerHTML",
                        '<font color="red" size="2"><b>This thread is closed to replies. But your moderator status allows you to reply to this thread.</b></font>'
                    );
                    this._$("form:eq(2) a:first").
                        attr("href", "/forums/index.php?fn=open_thread&t=" + this._threadData.threadId).
                        html("open thread");
                }
                else {
                    this._$(".forumnamearea p:first").html("");
                    this._$("form:eq(2) a:first").
                        attr("href", "/forums/index.php?fn=close_thread&t=" + this._threadData.threadId).
                        html("close thread");
                }
            }
            else {
                this._$("#namfox-quick-reply-response-message").html(response);
                
                this._$("#namfox-quick-reply-response-message a").each(
                    function() {
                        // Relative URLs need to be re-based at "/forums/"
                        var href = this.getAttribute("href");
                        if (!$.startsWith(href, "/")) {
                            this.setAttribute("href", "/forums/" + href);
                        }
                    }
                );
                
                this._endProcess();
                return;
            }
        }
        else {
            // Normal posts now go straight back to the thread screen.
            // Now no more requests are needed to get the new post!
            this._appendPost(xhr);
        }
        
        if (selectedCaption) {
            var bugStatusDropDownList = this._$("select[name=bug_status]");
            
            $.assert(bugStatusDropDownList.length > 0, "If a selected caption was set, there should be a drop down list for bug statuses.");
            
            if (bugStatusDropDownList.length > 0) {
                bugStatusDropDownList = bugStatusDropDownList.get(0);
                
                var comparisonValue = selectedCaption.value;
                
                var options = bugStatusDropDownList.options;
                
                for (var i = options.length - 1; i >= 0; --i) {
                    if (options[i].value === comparisonValue) {
                        bugStatusDropDownList.selectedIndex = i;
                        break;
                    }
                }
            }
            
            this._$("h4 span small").html(selectedCaption.getAttribute("html"));
        }
        else if (confirmed) {
            this._$("#bug_status").html('Bug Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Confirmed');
        }
    },
    
    _onUnload: function(event) {
        /// <summary>
        ///   An event handler for the pagehide event on the browser. Used to
        ///   track the quick reply message and store it if the user
        ///   accidentally leaves the page without submitting the message.
        /// </summary>
        
        var textarea = this._textArea.get(0);
        var threadId = this._threadData.threadId;
        if (!this._suppressUnload &&
            textarea.value &&
            textarea.value !== $.messages.getDefaultMessage().text) {
            
            $.info("Storing temporary message for thread " + threadId + " : " + textarea.value);
            
            // An easy way to check if the document that houses this text area is still active.
            if (textarea.clientWidth !== 0) {
                var selectionStart = textarea.selectionStart;
            }
            
            $.dataRepository.getData($.dataKeys.quickReplyMessage)[threadId] = {
                caretIndex: selectionStart || 0,
                message: textarea.value
            };
        }
        else {
            $.dataRepository.getData($.dataKeys.quickReplyMessage)[threadId] = null;
        }
    },
    
    _processModeratorRequest: function(operation) {
        /// <summary>
        ///   Encapsulates functionality common to closing and opening threads.
        /// </summary>
        /// <param name="operation" type="String">Either 'Close' or 'Open'.</param>
        
        var selectedCaption = null;
        var postData = {
            pst_subject: $.urlEncode(this._threadData.getSubjectForReply()),
            pst_body: $.urlEncode(this._textArea.val()),
            submit: "Confirm+" + operation,
            "function": operation.toLowerCase() + "_thread",
            threadid: this._threadData.threadId
        };
        
        var statusDropDownList = this._$("#namfox-quick-reply-bug-status");
        if (statusDropDownList.length > 0) {
            statusDropDownList = statusDropDownList.get(0);
            
            selectedCaption = statusDropDownList.options[statusDropDownList.selectedIndex];
            postData.thread_caption = selectedCaption.getAttribute("markup");
        }
        
        if (this._$("#namfox-quick-reply-signature").attr("checked")) {
            postData.usesig = "Y";
        }
        
        $.post(
            $.url.forum("/index.php"),
            postData,
            $.createCallback(this._onQuickReplySubmitted, this, selectedCaption),
            $.createCallback(this._onError, this, "reply")
        );
    },
    
    _reportStatus: function(status) {
        /// <summary>Reports the current status of the quick reply.</summary>
        
        this._$("#namfox-quick-reply-status").html(status);
    },
    
    attach: function() {
        /// <summary>Renders the quick reply on the view thread page.</summary>
        
        var config = $.getConfig().quickReply;
        if (!config.enabled) {
            return;
        }
        
        // If the thread does not have a quick reply link and the user
        // is not a moderator, then don't add quick reply.
        if (this._$("a[name=quickreply_link]").length === 0 &&
            !this._threadData.isUserModerator()) {
            return;
        }
        
        $.info("Adding quick reply to " + this._htmlDocument.location.href);
        
        var threadId = this._threadData.threadId;

        var html = "";
        html += '<div id="namfox-quick-reply-container">';
        html += '  <h4>NAMFox Quick Reply</h4>';
        html += '  <div id="namfox-quick-reply-response-message"></div>';
        html += '  <div id="namfox-quick-reply-core-container">';
        html += '    <div id="namfox-post-message-container"></div>';
        html += '    <div>';
        html += '      <textarea id="namfox-quick-reply-textarea"></textarea>';
        html += '    </div>';
        html += '    <div id="namfox-quick-reply-markup-bar"></div>';
        html += '  </div>';
        html += '  <div id="namfox-quick-reply-options-container">';
        html += '    <div>';
        html += '      <input id="namfox-quick-reply-signature" type="checkbox"></input>';
        html += '      <label for="namfox-quick-reply-signature">Append signature to this post [ members only ]</label>';
        html += '    </div>';
        
        if (this._threadData.isInBugForum()) {
            
            if (this._threadData.isUserModerator()) {
                var selectedValue = this._$("select[name=bug_status]").val();
                
                html += '<div style="padding-top: 10px;">';
                html += '  <input type="checkbox" style="visibility: hidden;"></input>';
                html += '  Change Status To: ';
                html += '  <select id="namfox-quick-reply-bug-status">';
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_orange.png&quot;/&gt; Unconfirmed", "Status: [img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] Unconfirmed", "Unconfirmed");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_red.png&quot;/&gt; Confirmed", "Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Confirmed", "Confirmed");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_yellow.png&quot;/&gt; Fix Pending", "Status: [img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img] Fix Pending", "Fix Pending");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_green.png&quot;/&gt; Resolve &ndash; Please Confirm", "Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Resolved &ndash; Please Confirm", "Resolved");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_black.png&quot;/&gt; Closed &ndash; Fixed", "Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed &ndash; Fixed", "Closed - Fixed");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_black.png&quot;/&gt; Closed &ndash; Unreproducible", "Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed &ndash; Unreproducible", "Closed - Unreproducible");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_black.png&quot;/&gt; Closed &ndash; Duplicate", "Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed &ndash; Duplicate", "Closed - Duplicate");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_black.png&quot;/&gt; Closed &ndash; Won\'t Fix", "Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed &ndash; Won\'t Fix", "Closed - Won\'t Fix");
                html += this._buildOptionElement(selectedValue, "Status: &lt;img alt=&quot;&quot; src=&quot;http://i.neoseeker.com/d/icons/flag_blue.png&quot;/&gt; Bad Bugged", "Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Bad Bugged", "Bad Bugged");
                html += '  </select>';
            }
            else if ($.contains(this._$("#bug_status").html(), "Unconfirmed")) {
                html += '<div>';
                html += '  <input id="namfox-quick-reply-bug-confirm" type="checkbox"></input>';
                html += '  <label for="namfox-quick-reply-bug-confirm">Can you confirm this bug?</label>';
            }
            
            html += '</div>';
        }

        html += '  </div>';
        html += '  <div id="namfox-quick-reply-submission">';
        html += '    <img';
        html += '      id="namfox-quick-reply-loading-image"';
        html += '      src="chrome://namfox/skin/posting/loading-eef0f9.gif" />';
        html += '    <span id="namfox-quick-reply-status"></span>';
        html += '  </div>';
        html += '  <div id="namfox-quick-reply-action-container">';
        html += '    <div id="namfox-quick-reply-normal-action">';
        html += '      <input id="namfox-quick-reply-submit" type="button" value="Quick Reply" />';
        html += '      <input id="namfox-quick-reply-quick-preview" type="button" value="Preview" />';
        html += '      <input id="namfox-quick-reply-full-preview" type="button" value="Go to Full Reply" />';
        html += '    </div>';
        
        // Add moderator items if the user is a moderator of the forum in which this quick reply is rendered.
        if (this._threadData.isUserModerator()) {
            html += '<div id="namfox-quick-reply-moderator-action">';
            html += '  <input id="namfox-quick-reply-close" type="button" value="Quick Close" />';
            html += '  <input id="namfox-quick-reply-open" type="button" value="Quick Open" />';
            html += '</div>';
        }
        
        html += '  </div>';
        html += '</div>';
        
        $.verbose("Adding Quick Reply.");
        this._$("#forumsearch").before(html);

        // Used to make sure users have a similar experience with the '@' notification that they do on native Neoseeker text areas.
        Sandbox.evalOnPage(
            this._htmlDocument,
            'refresh_friendsArray(jQuery("#namfox-quick-reply-textarea"))',
            {
                jQuery: "$",
                refresh_friendsArray: "refresh_friendsArray"
            });
        
        // Now that the quick reply has been added, find the reply message
        // that the moderator has set for the forum and add it to the UI.
        // HACK: Send the current cookie string along to supplement the
        // request. Otherwise, this call will not work due to the browser's
        // not sending cookies when "Accept third-party cookies" is unchecked.
        var that = this;
        $.get(
            $.url.forum("/index.php?fn=post_reply&t=" + threadId),
            null,
            function (xhr) {
                var messageText = xhr.responseText.match(QuickReply.replyMessageExpr);
                
                if (!messageText) {
                    throw new Error("Could not retrieve the moderator's message to display with the quick reply.");
                }
                
                that._$("#namfox-post-message-container").html(messageText);
            },
            null,
            null,
            this._htmlDocument.cookie
        );
        
        this._textArea = this._$("#namfox-quick-reply-textarea");
        this._htmlDocument.__namfox__.autoComplete.attachSingle(this._textArea.get(0));
        
        var adjustedWidth = ((100 - parseInt(config.textAreaWidth.substr(0, 2))) - 2) + "%";
        this._$("#namfox-quick-reply-container").css("marginRight", adjustedWidth);
        this._textArea.
            attr("rows", config.textAreaHeight).
            css("width", "98%").
            val($.messages.getDefaultMessage().text);
        this._$("#namfox-quick-reply-signature").attr("checked", config.useSignatureChecked);
        
        this._$("#namfox-quick-reply-submit").click($.createCallback(this._onQuickReplyClicked, this));
        this._$("#namfox-quick-reply-quick-preview").click($.createCallback(this._onPreviewClicked, this));
        this._$("#namfox-quick-reply-full-preview").click($.createCallback(this._onFullReplyClicked, this));
        
        if (this._threadData.isUserModerator()) {
            this._$("#namfox-quick-reply-close").click($.createCallback(this._onCloseClicked, this));
            this._$("#namfox-quick-reply-open").click($.createCallback(this._onOpenClicked, this));
        }
        
        var resetCursor = true;
        
        // If the user has told quick reply to remember old messages,
        // then see if a previous message exists. If so, populate
        // the text area with that message.
        if (config.rememberMessage) {
            $.info("Recalling previous message for quick reply.");
            
            var data = $.dataRepository.getData($.dataKeys.quickReplyMessage);
            
            if (data && data[threadId]) {
                this._textArea.attr({
                    value: data[threadId].message,
                    selectionStart: data[threadId].caretIndex,
                    selectionEnd: data[threadId].caretIndex
                });
                resetCursor = false;
            }
            
            this._htmlDocument.defaultView.addEventListener(
                "unload",
                $.createCallback(this._onUnload, this),
                false
            );
        }

        if (resetCursor) {
            this._moveCaretToBeginning();
        }

        this._$("#namfox-quick-reply-markup-bar").append(
            Markup.create(
                this._textArea,
                MarkupLocation.QuickReply,
                Markup.bold,
                Markup.italics,
                Markup.underline,
                Markup.quote,
                Markup.linkName,
                Markup.customMessages
            )
        );
    }
};

}});